/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年5月23日
 */
package com.massyframework.beanskin.util.classloading;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.ModuleIdentifier;
import com.massyframework.beanskin.SimpleModuleIdentifier;
import com.massyframework.beanskin.util.CompoundEnumeration;

/**
 * 加载类时，采用自身优先策略的类加载器.
 * 
 * @author huangkh
 *
 */
public class SelfFirstClassLoader extends URLClassLoader {

	private static Logger LOG = LoggerFactory.getLogger(SelfFirstClassLoader.class);
	protected static final ModuleIdentifier KERNEL =
			SimpleModuleIdentifier.createModuleIdentifierWithKernel();
	public static final String PATH = "BEANSKIN-INF/";
	
	private ClassLoader parentClassLoader;
	private final ResourceLocateFilterRepository resourceLocateFilterRepo;

	/**
	 * 使用{@code builder}创建新实例
	 * @param builder {@link Builder}
	 */
	protected SelfFirstClassLoader(Builder builder) {
		super(builder.urls.toArray(new URL[builder.urls.size()]), null); 
		this.parentClassLoader = Objects.requireNonNull(builder.parentClassLoader, "\"parentClassLoader\" cannot be null.");
		this.resourceLocateFilterRepo = builder.resourceLocateFilterRepo;
	}

	
	/**
	 * 查找类	
	 */
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		Class<?> result = null;
		try {
			result = this.findClassWithUcp(name);
		}catch(ClassNotFoundException e) {
			
		}
		
		if (result == null) {
			// customize found class.
	        try {
	            result = this.customizeFindClass(name);
	            if (result != null){
	                return result;
	            }
	        }catch (ClassNotFoundException ignored) {
	        	if (LOG.isWarnEnabled()) {
	        		LOG.warn("cannot found Class with customize: ".concat(name)+".");
	        	}
	        }
		}
		
		if (result == null) {
			// parentClassLoader found.
			if (result == null) {
				if (this.parentClassLoader != null) {
					try {
						result = this.parentClassLoader.loadClass(name);
					}catch(ClassNotFoundException e) {
						
					}
				}
			}
		}
		
		if (result == null) {
			//抛出例外
	        throw new ClassNotFoundException(
	                String.format("cannot found %s from [%s].", name, this));
		}
		return result;
	}


	@Override
	public final URL findResource(String name) {
		URL result = this.findResourceWithUcp(name);
		if (result == null){
            if (!this.isBEANSKIN_INF(name)) {
            	ResourceLocateFilter filter = this.findResourceLocateFilter(name);
                result = this.customizeFindResource(name, filter);
                
                if (result == null){
                    boolean isNotNull = this.getParentClassLoader() != null;
                    if (isNotNull) {
                        try {
                            Enumeration<URL> em = this.getParentClassLoader().getResources(name);
                            while (em.hasMoreElements()){
                                result = this.checkExcludeURL(em.nextElement());
                                if (result != null){
                                    break;
                                }
                            }
                        }catch(IOException ignored){

                        }
                    }
                }
            }
        }

        return result;
	}


	@Override
	public final Enumeration<URL> findResources(String name) throws IOException {
		boolean isExcludePath = this.isBEANSKIN_INF(name);
		
		if (isExcludePath) {
			return this.findResourcesWithUcp(name);
		}else {
			ResourceLocateFilter filter = this.findResourceLocateFilter(name);
			return this.findResources(name, filter);
		}
	}
	
	/**
	 * 
	 * @param name
	 * @param filter
	 * @return
	 * @throws IOException
	 */
	public Enumeration<URL> findResources(String name, ResourceLocateFilter filter) throws IOException{
		boolean hasParent = this.parentClassLoader != null;
		boolean isParentExclude = this.isExcludePaths(KERNEL, filter);
		
		int size = hasParent ? 3: 2;
		if (isParentExclude) {
			size = size -1;
		}
		
		@SuppressWarnings("unchecked")
		Enumeration<URL>[] ems = new Enumeration[size];
		ems[0] = this.findResourcesWithUcp(name);
		ems[1] = this.customizeFindResources(name, filter);
		if (!isParentExclude) {
			ems[2] = this.parentClassLoader.getResources(name);
		}
		return new CompoundEnumeration<URL>(ems);
	}
	
	/**
     * 自定义的查找类
     * @param name {@link String},类名
     * @return {@link Class}
     * @throws ClassNotFoundException 类未找到时抛出的例外
     */
    protected Class<?> customizeFindClass(String name) throws ClassNotFoundException{
        return null;
    }
    
    /**
     * 自定义查找资源
     * @param name {@link String},资源名称
     * @param filter {@link ResourceLocateFilter},资源定位过滤器
     * @return {@link URL}
     */
    protected URL customizeFindResource(String name, ResourceLocateFilter filter){
        return null;
    }
    
    /**
     * 自定义查找资源
     * @param name {@link String},资源名称
     * @param filter {@link ResourceLocateFilter},资源定位过滤器
     * @return {@link Enumeration}
     * @throws IOException io读写时产生的例外
     */
    protected Enumeration<URL> customizeFindResources(String name, ResourceLocateFilter filter) throws IOException{
        return null;
    }

	/**
     * 使用ucp查找类
     * @param name {@link String},类名
     * @return {@link Class}
     * @throws ClassNotFoundException 类没有找到时抛出的例外
     */
    protected Class<?> findClassWithUcp(String name) throws ClassNotFoundException{
        return super.findClass(name);
    }
    
    /**
     * 使用ucp的findResource方法查找
     * @param name {@link String},资源名称
     * @return {@link URL}
     */
    protected URL findResourceWithUcp(String name){
        return super.findResource(name);
    }
    
    /**
     * 调用ucp的findResources方法
     * @param name {@link String}, 资源名称
     * @return {@link Enumeration}
     * @throws IOException
     */
    protected Enumeration<URL> findResourcesWithUcp(String name) throws IOException{
        return super.findResources(name);
    }
    
    /**
     * 判断是否是BEANSKIN_INF目录
     * @param name {@link String}, 资源名称
     * @return {@link boolean},返回{@code true}表示需要排除，否则返回{@code false}
     */
    protected boolean isBEANSKIN_INF(String name){
        return name.startsWith(PATH);
    }
    
    /**
	 * 查找对应{@code name}资源的资源定位过滤器
	 * @param name {@link String},资源名称
	 * @return {@link ResourceLocateFilter},可能返回null.
	 */
	protected ResourceLocateFilter findResourceLocateFilter(String name) {
		if (this.resourceLocateFilterRepo == null) return null;
		return this.resourceLocateFilterRepo.findResourceLocateFilter(name);
	}
	
	/**
	 * 判断{@code moduleIdentifier}是否为{@link ResourceLocateFilter}排斥搜索的模块
	 * @param moduleIdentifier {@link ModuleIdentifier},模块标识符
	 * @param filter {@link ResourceLocateFilter}，资源定位过滤器
	 * @return {@link boolean},返回{@code true}表示是排斥模块，否则返回{@code false}
	 */
	protected boolean isExcludePaths(ModuleIdentifier moduleIdentifier, ResourceLocateFilter filter) {
		if (filter == null) return false;
		return filter.isExclude(moduleIdentifier);
	}
    
    /**
     * 检查是否需要排除的URL,如果是，则返回null.
     * @param resource {@link URL}, 资源的URL
     * @return {@link URL}, 如果是需要排除的URL,则null.
     */
    protected URL checkExcludeURL(URL resource){
        return resource;
    }
    	
    /**
     * 父类加载器
     * @return {@link ClassLoader}
     */
    protected ClassLoader getParentClassLoader() {
    	return this.parentClassLoader;
    }
    	
	/**
	 * 创建构建器实例
	 * @return {@link Builder}
	 */
	public static Builder newBuilder() {
		return new Builder();
	}
	
	/**
	 * 构建器
	 * @author huangkh
	 *
	 */
	public static class Builder {
		private List<URL> urls;
		private ClassLoader parentClassLoader;
		private ResourceLocateFilterRepository resourceLocateFilterRepo;
		
		/**
		 * 添加jar资源对应的URL
		 * <p>
		 * jar资源可以是jar包，或者目录形式的jar。
		 * @param url {@link URL}
		 * @return {@link Builder}
		 */
		public Builder addURL(URL url) {
			if (url != null) {
				if (this.urls == null) {
					this.urls = new ArrayList<URL>();
				}
				this.urls.add(url);
			}
			
			return this;
		}
		
		/**
		 * 添加jar资源对应的URL集合
		 * <p>
		 * jar资源可以是jar包，或者目录形式的jar。
		 * @param urls {@link Collection}
		 * @return {@link Builder}
		 */
		public Builder addURLs(Collection<URL> urls) {
			if (urls != null) {
				if (this.urls == null) {
					this.urls = new ArrayList<URL>();
				}
				
				this.urls.addAll(urls);
			}
			
			return this;
		}
		
		/**
		 * 设置资源定位过滤器资源库
		 * @param value {@link ResoruceLocateFilterRepository}, 资源定位过滤器资源库
		 * @return {@link ModuleClassLoaderBuilder}
		 */
		public Builder resourceLocateFilterRepository(ResourceLocateFilterRepository value) {
			this.resourceLocateFilterRepo = value;
			return this;
		}
		
		/**
		 * 设置父类加载器
		 * @param value {@link ClassLoader}
		 * @return {@link Builder}
		 */
		public Builder parentClassLoader(ClassLoader value) {
			this.parentClassLoader = value;
			return this;
		}
		
		/**
		 * 创建SelfFirstClasssLoader 
		 * @return {@link SelfFirstClassLoader}
		 */
		public SelfFirstClassLoader build() {
			return new SelfFirstClassLoader(this);
		}
	}
}
